此篇會觀察模型做評估時使用evaluate之運作。
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
model = Sequential([
layers.Dense(512, activation="relu")
])
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.build(input_shape=(None, 784))
model.add(layers.Dense(10, activation="softmax"))
model.add(layers.Dense(20, activation="relu"))
model.fit(train_images, train_labels, epochs=5, batch_size=128)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"test_acc: {test_acc}")
(1)
依此範例,首先會到 keras.engine.training.Model.evaluate 函式主體,會將傳遞的參數與訊號傳入keras.engine.data_adapter.DataHandler建構子中做設定,在做模型訓練時也是會將訓練函式的參數包成data_adapter.DataHandler 實體,這意味著模型執行預測也可以批次設定來執行。
(2)
而參數也可以設定callback物件,如果傳入的 callbacks 參數list不是繼承 keras.callbacks.CallbackList 類別的實體 list,則會進行將callbacks 參數融入keras.callbacks.CallbackList 類別的動作,此類別也是 callback的Container。融入的過程中,也和訓練時一樣,最後keras.callbacks.CallbackList 實體一定會包含keras.callbacks.ProgbarLogger類別實體,與keras.callbacks.History類別實體。
(3)
接著叫用keras.engine.training.Model._get_test_function_runner函式,決定好每個test step所要使用的test_function_runner。這邊利用類似closure的方式將函式預先保存至記憶體,在跑批次時將會使用到此塊記憶體。
到此都和 model.predict 非常類似。
比較不同的是,這邊會將保留的記憶體函式包到 keras.engine.training.Model._TestFunction 類別裡,裡面還有子函式run_step供進一步使用。
(4)
測試主體如下:
for (_,dataset_or_iterator,) in data_handler.enumerate_epochs(): # 指定1個epoch.
for step in data_handler.steps():
logs = test_function_runner.run_step(
dataset_or_iterator,
data_handler,
step,
self._pss_evaluation_shards,
)
(5)
迴圈中主要會跑test_function_runner.run_step,會執行 keras.engine.training.Model.test_step函式,會直接到keras.engine.data_adapter.unpack_x_y_sample_weight,將傳入的資料依照list或其它型別集合轉成tuple回傳。
模型call函式一開始執行keras.engine.sequential._build_graph_network_for_inferred_shape,把input的張量維度設定好,接續至 keras.engine.functional._run_internal_graph 找出每個模型所建好的Layer node,依序執行 Layer.call。依本例為依序執行二層keras.layers.core.dense.call,主要做輸入input與本身Layer.kernel 的內積並回傳,供應用程式主體使用。(每層計算完會存到 keras.engine.sequential.outputs)
接下來會執行 keras.engine.training.Model.compiled_loss, 叫用eras.engine.training.Model之前所設定的LossesContainer物件,呼叫keras.engine.compile_utils.LossesContainer.call,進而執行所設定的keras.losses類別物件call 函式。
因為設定的loss function名稱為"sparse_categorical_crossentropy", 所以會進入keras.losses.sparse_categorical_crossentropy中。
計算完 loss function,程式執行keras.utils.losses_utils.compute_weighted_loss做 weighted loss,再使用keras.utils.losses_utils.scale_loss_for_distribution做Scales。大致上就是在做 weight 相關的計算。
計算結果後,透過處理LOG的 callback function,將回傳LOG資訊print出來。